﻿// Partial Copyright (c) LogoUI Software Solutions LTD
// Partial Copyright (c) Puzzlehead LTD
// Vlad Spivak, Igal Abachi
// This source file is the part of LogoFX Framework http://logofx.codeplex.com
// See accompanying licences and credits.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace LogoFX.Data
{
    public static class Extensions
    {
        /// <summary>
        /// Clones the source contents into the destination's , 
        /// while notifying to the gui on all the diffs between the source and the destination
        /// </summary>
        /// <remarks>Generalized version - most constraints reduced to serve maximum usecases</remarks>
        /// <typeparam name="T">Destination type</typeparam>
        /// <typeparam name="TW">source type</typeparam>
        /// <param name="destination">The destination list</param>
        /// <param name="source">The source list</param>
        /// <param name="creator">Factory method to produce T based on TW</param>
        /// <param name="comparator">Predicate to compare instances of T and TW</param>
        public static void Merge<T, TW>(this IList<T> destination, IList<TW> source, Func<TW, T> creator, Func<T, TW, bool> comparator)
            where T : class
            where TW : class
        {
            if (comparator == null)
                throw new ArgumentException("comparator");
            if (creator == null)
                throw new ArgumentException("creator");
            if (source == null)
                throw new ArgumentException("source");

            //make a copy for thread saftey, so that it won't be changed from another thread in the middle of the enumeration
            //fixes: InvalidOperation_EnumFailedVersion: "Collection was modified; enumeration operation may not execute."
            var destinationCopy = destination.ToList().Where(a => a != null);
            var sourceCopy = source.ToList().Where(a => a != null);

            IList<T> old = destinationCopy.Where(a => !sourceCopy.Any(b => comparator(a, b))).ToList();
            old.Apply(a => destination.Remove(a));

            source.Where(a => a != null).Apply(a =>
            {
                T found = destination.Where(b => b != null).FirstOrDefault(b => comparator(b, a));
                if (found == null)
                    destination.Add(creator(a));
                else if (found is IMergeable<TW> &&
                    // ReSharper disable ConditionIsAlwaysTrueOrFalse
                    //in case T and TW is the same type those two can be the same object
                     !ReferenceEquals(found, a)
                    // ReSharper restore ConditionIsAlwaysTrueOrFalse
                    )
                    ((IMergeable<TW>)found).Merge(a);
            });


            //multiple bugs in logic, removed
#if OLD_CODE
            //remove nulls from the copies and get new count
            int destinationCopyCount = RemoveNulls(destinationCopy);
            int sourceCopyCount = RemoveNulls(sourceCopy);

            //remove the items from destination that doesn't exists in sourceCopy
            for (int i = 0; i < destinationCopyCount; i++)
            {
                T item = destinationCopy[i];

                //check if source contains the id from the destination
                bool sourceContainsId = false;

                for (int j = 0; j < sourceCopyCount; j++)
                {
                    if (comparator(item,sourceCopy[j]))
                    {
                        sourceContainsId = true;
                        break;
                    }
                }

                if (!sourceContainsId)
                {
                    //remove from the real collection, to notify changes
                    destination.Remove(item);

                    //make it on the copy too so that all the loops will be in sync
                    destinationCopy.Remove(item);
                    destinationCopyCount--;
                }
            }

            //add to destination the items from sourceCopy that doesn't exists in destination, and merge the rest
            for (int i = 0; i < sourceCopyCount; i++)
            {
                TW sourceItem = sourceCopy[i];

                bool destinationContainsId = false;
                T found = null;

                //check wheter the item from the source exists in the destination, and get that item from the destination
                for (int j = 0; j < destinationCopyCount; j++)
                {
                    T item = destinationCopy[j];
                    if (comparator(item,sourceItem))
                    {
                        destinationContainsId = true;
                        found = item;

                        break;
                    }
                }

                if (destinationContainsId)
                {
                    //item already exists, merge the change, and notify for changes
                    if (found is IMergeable<TW> &&
// ReSharper disable ConditionIsAlwaysTrueOrFalse
                        //in case T and TW is the same type those two can be the same object
                        !ReferenceEquals(found,sourceItem)
// ReSharper restore ConditionIsAlwaysTrueOrFalse
                        )
                        ((IMergeable<TW>)found).Merge(sourceItem);
                }
                else
                {
                    //create the new item
                    var newItem = creator(sourceItem);

                    //add to the real collection, to notify changes
                    destination.Add(newItem);

                    //make it on the copy too so that all the loops will be in sync
                    destinationCopy.Add(newItem);
                    destinationCopyCount++;
                }
            }


            #region -old code-
            /* 
            IList<int> ids = source.Where(a => a != null).Select(a => a.Id).ToList();
            IList<T> old = destination.Where(a => a != null && !ids.Contains(a.Id)).ToList();
            old.Where(a => a != null).Apply(a => destination.Remove(a));

            source.Where(a => a != null).Apply(a =>
            {
                T found = destination.Where(b => b != null).FirstOrDefault(b => b.Id == a.Id);
                if (found == null)
                    destination.Add(creator(a));
                else
                    found.Merge(a);
            });
            */
            #endregion
#endif
        }

#if OLD_CODE
        private static int RemoveNulls<T>(List<T> list) where T : class
        {
            for (int i = (list.Count - 1); i >= 0; i--)
            {
                T item = list[i];

                if (item == null)
                {
                    list.RemoveAt(i);
                }
            }

            int newCount = list.Count;

            //set the size of the internal array, to the exact size of the items
            list.Capacity = newCount;

            return newCount;
        }
#endif
    }
}
